<?php

/**
 * \file
 * Classe d'un bloc formulaire de recherche à partir d'une entité.
 */

/**
 * Génération automatique de formulaires d'administration (CRUD).
 */
namespace pilar\Scaffolding /* \cond */ ; /* \endcond */
{

/**
 * Bloc de recherche.
 *
 * \since 03/03/2009
 * \author Dirk MARSKI <dirk.marski@gmail.com>
 */
class SearchForm extends \pilar\Forms\Form {

/**
 * Membres d'entité présents dans le formulaire.
 *
 * \since 03/03/2009
 */
protected $_entityMembers = array();


/**
 * Membres de formulaire de recherche.
 *
 * \since 12/03/2009
 */
protected $_searchFormMembers = array();


/**
 * Valeur du champs de recherche pour une recherche simple.
 *
 * \since 05/03/2009
 */
protected $_easySearchFieldValue = '';


/**
 * Valeur qui stocke le meilleur score pour un résultat de recherche.
 *
 * \since 05/03/2009
 */
protected $_bestRating = 0;


/**
 * Recherche lancée ?
 *
 * \since 24/03/2009
 */
public $isSearching = false;


protected function loadDefaultParams() {
 parent::loadDefaultParams();

 $this->params->define(array(
  'entity' => '',  // Entité sur laquelle on souhaite faire des recherches.
  'memberNames' => array('default' => true),  // Noms des membres à afficher
                                              // dans le formulaire.
  'buttonLabel' => '', // Texte du bouton de confirmation.
  'advancedSearch' => false, // Recherche avancée sur l'entité.
  'rateResults' => false, // Est-ce qu'il faut calculer la pertinence des 
                          // résultats ?
  'inMultiForm' => false, // Permet de spécifier si le formulaire se trouve dans
                          // formulaire multiple.
  'paginationLength' => 20
 ));
}


protected function initialize() {
 if($this->get('buttonLabel') == '') {
  $this->params->buttonLabel = 'Rechercher';
 }
	$this->params->localId .= '_'.$this->get('entity')->get('name');
 
 parent::initialize();
}


protected function load() {
 parent::load();
 
 $this->newArea('Results');
 
 // Chargement des membres :
  $this->loadMembers();

 // Ajout de l'action en hidden :
  $action = new \pilar\SimpleFormMembers\TextBox(array(
   'name' => '_action',
   'hidden' => true
  ));
  $action->value = 'search';
  $this->appendAtom($action);
}


/**
 * Ajoute le bouton de confirmation.
 *
 * \since 24/03/2009
 */
private function appendSubmit() {
 if($this->get('independent')) {
  // Ajout du bouton de confirmation :
   $this->appendAtom(
    new \pilar\Forms\Button(array(
     'label' => $this->get('buttonLabel')
    ))
   );
 }
}


protected function buildContentRoot($domDoc) {
 // Si on n'est pas entrain de faire une recherche, on ajoute le bouton :
  if (!$this->isSearching) {
   $this->appendSubmit();
  }

 $domRF = $domDoc->createElement('SearchForm');
 $domForm = parent::buildContentRoot($domDoc);
 
 $domRF->appendChild($domForm);

 $domRF->appendChild($this->area('Results')->domNode($domDoc));
 
 return $domRF;
}


protected function actions() {
	$this->doLoad();

 parent::actions();

 if($this->postVarValue('_action_value') == 'search') {	
  // Chargement des membres depuis le formulaire :
   if($this->loadFromGui() == 'valid') {
    $this->searching();
    // Cas où le formulaire est valide :
     // Création de la requête :
      $objects = $this->searchRequest();
      if ($objects != false) {
       $this->dealWithResults($objects);
      }
   }
 }
}


/**
 * Traite les résultats de la recherche.
 *
 * \param $objects Objets qui résultent de la recherche.
 * \since 05/03/2009
 */
private function dealWithResults($objects) {
/* if ($this->get('rateResults') && !$this->get('advancedSearch')) { 
 	$o = array();
	 // Calcul de la pertinance.
	  foreach ($objects as $object) {
	 	 $o[]	=	$this->calculateObjectRating($object);
 	 }
	 
 	// Pertinence en % :
  	if ($this->_bestRating != 0) {
	   foreach ($o as $object) {
 	   $object->rating = intval($object->rating/$this->_bestRating*100);
 	  }
	  }

	 $o = $this->sortResults($o);
 } else {
 	$o = $objects;
 }*/
		
 $this->showResults($objects);
}


/**
 * Calcule la pertinence d'un objet. Mise à jour de la pertinance maxi.
 *
 * \param $object Objet à traiter.
 * \return Objet traité.
 * \since 05/03/2009
 */
private function calculateObjectRating($object) {
/*	// Alias de code (n = needle) :
  $n = $this->_easySearchFieldValue;
  
 $rating = 0;
 foreach ($this->_entityMembers as $em) {
 	foreach ($object->__get($em->get('name'))->fields() as $f) {
 		$l = strlen($f->get());
 		if ($l != 0) {
  		$rating += substr_count(downcase($f->get()), $n)/$l;
 		}
 	}
 }
 $object->rating = $rating;
 
 if ($rating > $this->_bestRating) {
 	$this->_bestRating = $rating;
 }*/

	return $object;
}


/**
 * Affiche les résultats de la recherche dans un bloc.
 *
 * \param $objects Objets qui résultent de la recherche.
 * \since 05/03/2009
 */
protected function showResults($objects) {
 $this->area('Results')->pushSubBlocks(
	 new \pilar\Scaffolding\ObjectCollectionListing(array(
   'objects' => $objects,
   'paginationLength' => $this->get('paginationLength'),
   'form' => $this
  ))
 );
}


/**
 * Charge les membres à partir des noms passés en paramètres et à partir
 * de la modélisation de l'entité.
 *
 * \since 03/03/2009
 **/
private function loadMembers() {
 // Détermination des noms de membres à prendre en compte :
  $names = array(); // Variable de résultat.
  $conf = $this->get('memberNames'); // Alias de code.
  // Sélection par défaut :
   if(!isset($conf['default']) || $conf['default']) {
    foreach($this->get('entity')->members as $m) {
     if($m->get('inForms') && $m->get('specialSearchMember') == false) {
      $names[] = $m->get('name');
     }
    }
   }
  // Modifications par soustractions :
   if(isset($conf['sub'])) $names = array_diff($names, $conf['sub']);
  // Modifications par ajout :
   if(isset($conf['add'])) $names = array_merge($names, $conf['add']);
   
 // Chargement des membres de formulaire :
  foreach($names as $name) {
   $this->joinEntityMember($this->get('entity')->member($name));
  }

 // Chargement d'un champs géneral dans le cas d'une recherche facile:
  if (!$this->get('advancedSearch')) {
   $this->addEasySearchField();
  }
}


/**
 * Ajoute un champs de recherche pour les recherches simples.
 *
 * \since 05/03/2009
 */
protected function addEasySearchField() {
 $this->appendAtom(
  new \pilar\SimpleFormMembers\TextBox(array(
   'name' => 'easySearch',
   'label' => 'champ de recherche',
   'notEmpty' => true
  ))
 );
}


/**
 * Inscrit un membre d'entité au formulaire.
 *
 * \param $m Membre d'entité.
 *
 * \since 03/03/2009
 */
final private function joinEntityMember(\pilar\Entities\Member $m) {
 $this->_entityMembers[] = $m;
 $sfm = $this->entityMember2formMember($m, array());
 $this->_searchFormMembers[] = $sfm;


 // Ajout du membre de formulaire au formulaire :
  if ($this->get('advancedSearch')) {
   $this->appendAtoms($sfm->formMembers());
  }
}


/**
 * Instancie un membre de formulaire en fonction d'un membre d'entité et
 * l'ajoute aux membres.
 *
 * \param $m Membre de l'entité à utiliser.
 * \return Membre de formulaire.
 * \since 03/03/2009
 */
protected function entityMember2formMember($m, $conf) {
 return $m->newSearchFormMember($conf);
}


/**
 * Effectue la recherche selon les données du formulaire.
 * Dans le cas d'une recherche simple, la valeur du champs sera injectée
 * dans tous les membres de formulaire chargés.
 *
 * \return Résultat de la recherche sous forme de collection d'objet,
 *         false si la requête est vide.
 * \since 05/03/2009
 */
private function searchRequest() {
	$objects = $this->get('entity')->newCollection();
	$request = '';

 // Si _easySearchFieldValue est différent de '', alors ce sera pris en compte
 // lors de la génération de la requête.
  $value = $this->_easySearchFieldValue;

	// Génération de la requête :
	 $first = true;
  foreach($this->_searchFormMembers as $sfm) {
   foreach($sfm->sqlExpressions($objects, $value) as $sqlExp) {
    if ($first) {
     $request = $sqlExp;
     $first = false;
    } else {
     $request = \ory($sqlExp, $request);
    }
   }
  }
 
	if ($request != '') {
  $objects->where($request);
	} else {
		return false;
	}

 return $objects;
}


public function loadFromGui() {
 if ($this->_status == 'unknown') {
  $s = parent::loadFromGui();
 // Cette fonction a été bcp simplifié, car il n'est pas facilement réalisable
 // de faire un chargement global de l'interface, les controlleurs étant trop
 // dépendant des membres de formulaires de recherche.
 // Ainsi, par défault, les erreurs de saisi seront ignorés. (la vérification
 // de cohérence des données se fait lors de la génération de la requête)
 // TODO: améliorer tout ceci, ça peut présenter une faille de sécurité.
  if (!$this->get('advancedSearch')) {
   $m = $this->member('easySearch');
   $v = $m->value();
   if (!$m->acceptsValue($v)) {
    $m->setSatus('invalid');
    $s = 'invalid';
   } else {
    $this->_easySearchFieldValue = downcase($v);
   }
  }
  return $s;
 } else {
  return $this->_status;
 }
}


/**
 * QuickSort pour les résultats des recherches en fonction de leur pertinence.
 *
 * \param $objects Résultats, sous forme de tableau de la recherche avec une pertinence.
 * \return Tableau des Résultats dans l'ordre décroissant.
 * \since 05/03/2009
 */
private function sortResults ($objects, $left = 0, $right = NULL) {
 usort($objects, array($this, 'objectComparison'));

 return array_reverse($objects);
}


private function objectComparison($a, $b) {
	if ($a->rating > $b->rating) {
		return 1;
	} else if ($a->rating > $b->rating) {
		return -1;
	} else {
		return 0;
	}
}


/**
 * Retourne le membre de fromulaire de recherche ayant le nom en question.
 *
 * \param $name Nom du membre.
 * \since 19/03/2009
 */
private function searchFormMember($name) {
 foreach($this->_searchFormMembers as $sfm) {
  if ($sfm->get('name') == $name) {
   return $sfm;
  }
 }
}


/**
 * Fonction gérant le cas d'une recherche.
 *
 * \since 24/03/2009
 */
private function searching() {
 $this->isSearching = true;
 foreach ($this->_searchFormMembers as $sfm) {
  $sfm->hideFormMembers();
 }
}


} // Class.

} // Namespace.

?>

